#include <GL/glew.h>
#include <GL/glut.h>
#include <cstdio>
#include <cmath>

#include "bitmap.h"
#include "wave.h"

void render(void)
{
	glViewport(0, 0, HF_RES, HF_RES);
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();
	gluOrtho2D(0.0, 1.0, 0.0, 1.0);
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	glLoadIdentity();
	glDisable(GL_DEPTH_TEST);
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb);

	glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
	glBlendFunc(GL_ONE, GL_ONE);
	glEnable(GL_BLEND);
	glClear(GL_COLOR_BUFFER_BIT);
	glBegin(GL_POINTS);
	int k;
	float t;
	switch (displayMode) {
		case 1:
			for (k = 0; k < wpNum; k++) {
				t = time - wp[k].bt;
				glColor3f(0.0f, 0.0f, 1.0f);
				glVertex2f(wp[k].x + t * wp[k].vx, wp[k].y + t * wp[k].vy);
			}
			break;
		case 2:
			for (k = 0; k < wpNum; k++) {
				t = time - wp[k].bt;
				glColor3f(0.0f, 0.0f, wp[k].amp * 300.0f);
				glVertex2f(wp[k].x + t * wp[k].vx, wp[k].y + t * wp[k].vy);
			}
			break;
		case 3:
			for (k = 0; k < wpNum; k++) {
				t = time - wp[k].bt;
				glColor3f(0.0f, 0.0f, wp[k].amp * 100.0f);
				glVertex2f(wp[k].x + t * wp[k].vx, wp[k].y + t * wp[k].vy);
			}
			break;
		default:
			for (k = 0; k < wpNum; k++) {
				t = time - wp[k].bt;
				glColor3f(0.0f, 0.0f, wp[k].amp);
				glVertex2f(wp[k].x + t * wp[k].vx, wp[k].y + t * wp[k].vy);
			}
			break;
	}
	glEnd();
	glDisable(GL_BLEND);

	
	// wave creation begin
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, fboTex[0]);
	glDrawBuffer(GL_COLOR_ATTACHMENT1);
	glUseProgram(xFilter);
	glBegin(GL_QUADS);
	glTexCoord2f(0.0f, 0.0f);
	glVertex2f(0.0f, 0.0f);
	glTexCoord2f(1.0f, 0.0f);
	glVertex2f(1.0f, 0.0f);
	glTexCoord2f(1.0f, 1.0f);
	glVertex2f(1.0f, 1.0f);
	glTexCoord2f(0.0f, 1.0f);
	glVertex2f(0.0f, 1.0f);
	glEnd();
	glUseProgram(0);	


	glBindTexture(GL_TEXTURE_2D, fboTex[1]);
	glDrawBuffer(GL_COLOR_ATTACHMENT2);
	glUseProgram(yFilter);
	glBegin(GL_QUADS);
	glTexCoord2f(0.0f, 0.0f);
	glVertex2f(0.0f, 0.0f);
	glTexCoord2f(1.0f, 0.0f);
	glVertex2f(1.0f, 0.0f);
	glTexCoord2f(1.0f, 1.0f);
	glVertex2f(1.0f, 1.0f);
	glTexCoord2f(0.0f, 1.0f);
	glVertex2f(0.0f, 1.0f);
	glEnd();
	glUseProgram(0);

	glReadBuffer(GL_COLOR_ATTACHMENT2);
	glBindBufferARB(GL_PIXEL_PACK_BUFFER_EXT, vb);
	glReadPixels(0, 0, HF_RES, HF_RES, DATA_FORMAT, DATA_TYPE, 0);
	glBindBufferARB(GL_PIXEL_PACK_BUFFER_EXT, 0);
	glReadBuffer(GL_NONE);
	// wave creation end
	
	// gradient begin
	glBindTexture(GL_TEXTURE_2D, fboTex[2]);
	glDrawBuffer(GL_COLOR_ATTACHMENT3);
	glUseProgram(gradient);
	glBegin(GL_QUADS);
	glTexCoord2f(0.0f, 0.0f);
	glVertex2f(0.0f, 0.0f);
	glTexCoord2f(1.0f, 0.0f);
	glVertex2f(1.0f, 0.0f);
	glTexCoord2f(1.0f, 1.0f);
	glVertex2f(1.0f, 1.0f);
	glTexCoord2f(0.0f, 1.0f);
	glVertex2f(0.0f, 1.0f);
	glEnd();
	glUseProgram(0);

	glReadBuffer(GL_COLOR_ATTACHMENT3);
	glBindBufferARB(GL_PIXEL_PACK_BUFFER_EXT, nb);
	glReadPixels(0, 0, HF_RES, HF_RES, DATA_FORMAT, DATA_TYPE, 0);
	glBindBufferARB(GL_PIXEL_PACK_BUFFER_EXT, 0);
	glReadBuffer(GL_NONE);
	// gradient end
	
    // caustics begin
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, fboTex[2]);
	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_2D, fboTex[3]);
	glDrawBuffer(GL_COLOR_ATTACHMENT4);
	glUseProgram(caustics);
	glBegin(GL_QUADS);
	glTexCoord2f(0.0f, 0.0f);
	glVertex2f(0.0f, 0.0f);
	glTexCoord2f(1.0f, 0.0f);
	glVertex2f(1.0f, 0.0f);
	glTexCoord2f(1.0f, 1.0f);
	glVertex2f(1.0f, 1.0f);
	glTexCoord2f(0.0f, 1.0f);
	glVertex2f(0.0f, 1.0f);
	glEnd();
	glUseProgram(0);

	glActiveTexture(GL_TEXTURE0);
	glReadBuffer(GL_COLOR_ATTACHMENT4);
	glBindBufferARB(GL_PIXEL_PACK_BUFFER_EXT, cb);
	glReadPixels(0, 0, HF_RES, HF_RES, DATA_FORMAT, DATA_TYPE, 0);
	glBindBufferARB(GL_PIXEL_PACK_BUFFER_EXT, 0);
	glReadBuffer(GL_NONE);
	// caustics end 

	glDrawBuffer(GL_COLOR_ATTACHMENT5);
	glEnable(GL_BLEND);
	glClear(GL_COLOR_BUFFER_BIT);
	glColor3f(CAUSTICS, CAUSTICS, CAUSTICS);
	glEnableClientState(GL_VERTEX_ARRAY);
	glBindBufferARB(GL_ARRAY_BUFFER_ARB, cb); 
	glVertexPointer(2, DATA_TYPE, 4 * TYPE_SIZE, 0);
	glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
	glDrawArrays(GL_POINTS, 0, HF_RES * HF_RES);
	glDisableClientState(GL_VERTEX_ARRAY);
	glDisable(GL_BLEND);

	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, fboTex[5]);
	glDrawBuffer(GL_COLOR_ATTACHMENT6);
	glUseProgram(cFilter);
	glUniform1iARB(glGetUniformLocationARB(cFilter, "showTex"), displayMode == 7);
	glBegin(GL_QUADS);
	glTexCoord2f(0.0f, 0.0f);
	glVertex2f(0.0f, 0.0f);
	glTexCoord2f(1.0f, 0.0f);
	glVertex2f(1.0f, 0.0f);
	glTexCoord2f(1.0f, 1.0f);
	glVertex2f(1.0f, 1.0f);
	glTexCoord2f(0.0f, 1.0f);
	glVertex2f(0.0f, 1.0f);
	glEnd();
	glUseProgram(0);



	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
	glDrawBuffer(GL_BACK);
	glEnable(GL_DEPTH_TEST);
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
	glPopMatrix();
	glViewport(0, 0, windowWidth, windowHeight);

	if (displayMode == 0) {
		glEnableClientState(GL_VERTEX_ARRAY);
		glEnableClientState(GL_NORMAL_ARRAY);

		glBindBufferARB(GL_ARRAY_BUFFER_ARB, vb); 
		glVertexPointer(3, DATA_TYPE, 4 * TYPE_SIZE, 0);

		glBindBufferARB(GL_ARRAY_BUFFER_ARB, nb);
		glNormalPointer(DATA_TYPE, 4 * TYPE_SIZE, 0);

		glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, eb); 

		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, poolBottom);
		glActiveTexture(GL_TEXTURE1);
		glBindTexture(GL_TEXTURE_2D, fboTex[6]);
		glUseProgramObjectARB(water);
		glUniform4fARB(glGetUniformLocationARB(water, "eyePosition"), eyePos[0], eyePos[1], eyePos[2], eyePos[3]);
		glUniform1iARB(glGetUniformLocationARB(water, "transparent"), transparent);
		glDrawElements(GL_TRIANGLES, maxIndex, GL_UNSIGNED_INT, 0);
		glUseProgramObjectARB(0);

		glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0); 
		glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);

		glDisableClientState(GL_NORMAL_ARRAY);
		glDisableClientState(GL_VERTEX_ARRAY);
	}
	else {
		glBindTexture(GL_TEXTURE_2D, fboTex[displayMode - 1]);
		glEnable(GL_TEXTURE_2D);
		glColor3f(1.0f, 1.0f, 1.0f);
		glBegin(GL_QUADS);
		glTexCoord2f(0.0f, 0.0f);
		glVertex3f(0.0f, 0.0f, 0.0f);
		glTexCoord2f(1.0f, 0.0f);
		glVertex3f(1.0f, 0.0f, 0.0f);
		glTexCoord2f(1.0f, 1.0f);
		glVertex3f(1.0f, 1.0f, 0.0f);
		glTexCoord2f(0.0f, 1.0f);
		glVertex3f(0.0f, 1.0f, 0.0f);
		glEnd();
		glDisable(GL_TEXTURE_2D);
	}

	if (transparent) {
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, poolBottom);
		glActiveTexture(GL_TEXTURE1);
		glBindTexture(GL_TEXTURE_2D, fboTex[6]);
		glUseProgramObjectARB(bottom);
		glUniform4fARB(glGetUniformLocationARB(bottom, "eyePosition"), eyePos[0], eyePos[1], eyePos[2], eyePos[3]);
		glBegin(GL_QUADS);
		glTexCoord2f(0.0f, 0.0f);
		glVertex3f(0.0f, 0.0, -0.2f);
		glTexCoord2f(1.0f, 0.0f);
		glVertex3f(1.0f, 0.0, -0.2f);
		glTexCoord2f(1.0f, 1.0f);
		glVertex3f(1.0f, 1.0, -0.2f);
		glTexCoord2f(0.0f, 1.0f);
		glVertex3f(0.0f, 1.0, -0.2f);
		glEnd();
		glUseProgramObjectARB(0);
	}
/*
	glActiveTexture(GL_TEXTURE0);
	glColor3f(1.0f, 0.5f, 0.0f);
	glPushMatrix();
	glTranslatef(lightPos[0], lightPos[1], lightPos[2]);
	glutSolidSphere(0.1, 16, 16);
	glPopMatrix();
	glColor3f(1.0f, 1.0f, 1.0f);
*/
}


void idle ()
{
	//fps.update(60.0);

	if (!systemPaused) {
		iteration();
	}
	glutPostRedisplay();
}

void display ()
{
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);

	glTranslatef(0.0f, 0.0f, -cameraR);
	glRotatef(cameraV - 90.0, 1.0f, 0.0f, 0.0f);
	glRotatef(270.0 - cameraH, 0.0f, 0.0f, 1.0f);
	glTranslatef(-0.5f, -0.5f, 0.0f);

	render();
	if (renderParticles || fading > 0.0f) renderWaveParticles();

//	renderText();
	glutSwapBuffers();
}

void clickToGenerate(int x, int y)
{
	double theta, phi, top, right, dn, dt;
	double x1, y1, z1, x2, y2, z2, x3, y3;

	theta = cameraH * PI_180;
	phi = cameraV * PI_180;
	if (phi > -epsilon && phi < epsilon) return;

	x1 = 0.5 + cameraR * cos(theta) * cos(phi);
	y1 = 0.5 + cameraR * sin(theta) * cos(phi);
	z1 = cameraR * sin(phi);

	top = cameraNear * tan(0.5 * cameraFovy * PI_180);
	right = top / windowHeight * windowWidth;
	dn = top / windowHeight * (windowHeight - y * 2);
	dt = right / windowWidth * (x * 2 - windowWidth);

	x2 = 0.5 + (cameraR - cameraNear) * cos(theta) * cos(phi) - dn * cos(theta) * sin(phi) - dt * sin(theta);
	y2 = 0.5 + (cameraR - cameraNear) * sin(theta) * cos(phi) - dn * sin(theta) * sin(phi) + dt * cos(theta);
	z2 = (cameraR - cameraNear) * sin(phi) + dn * cos(phi);

	x3 = x1 - z1 / (z2 - z1) * (x2 - x1);
	y3 = y1 - z1 / (z2 - z1) * (y2 - y1);

	if (x3 > 0.0 && x3 < 1.0 && y3 > 0.0 && y3 < 1.0) {
		if (generating) {
			float d = sqrt((x3 - genX) * (x3 - genX) + (y3 - genY) * (y3 - genY));
			if (d < PARTICLE_RADIUS)
				generateNewParticle(x3, y3, MAX_AMP / PARTICLE_RADIUS * d);
			else
				generateNewParticle(x3, y3, MAX_AMP);
		}
		else
			generateNewParticle(x3, y3, MAX_AMP);
		genX = x3; genY = y3;
	}
}


void mouse(int button, int state, int x, int y)
{
	if (button == GLUT_LEFT_BUTTON) {
		if (state == GLUT_DOWN) {
			clickToGenerate(x, y);
			generating = 1;
		}
		else if (state == GLUT_UP) {
			generating = 0;
		}
	}
	else if (button == GLUT_RIGHT_BUTTON) {
		if (state == GLUT_DOWN) {
			cameraSetting = 1;
			mouseOX = x; mouseOY = y;
			cameraTH = cameraH; cameraTV = cameraV;
		}
		else if (state == GLUT_UP) {
			cameraSetting = 0;
		}
	}
	else if (button == GLUT_MIDDLE_BUTTON) {
		if (state == GLUT_DOWN) {
			cameraSetting = 2;
			mouseOX = x; mouseOY = y;
			cameraTR = cameraR;
		}
		else if (state == GLUT_UP) {
			cameraSetting = 0;
		}
	}
}

void motion(int x, int y)
{
	if (generating) {
		clickToGenerate(x, y);
	}
	else if (cameraSetting == 1) {
		cameraH = cameraTH - 0.5 * (x - mouseOX);
		if (cameraH < -360.0) cameraH += 360.0;
		else if (cameraH > 360.0) cameraH -= 360.0;
		cameraV = cameraTV + 0.5 * (y - mouseOY);
		if (cameraV < -90.0 + epsilon) cameraV = -90.0 + epsilon;
		else if (cameraV > 90.0 - epsilon) cameraV = 90.0 - epsilon;
		eyePos[0] = cameraR * cos(cameraH * PI_180) * cos(cameraV * PI_180) + 0.5f;
		eyePos[1] = cameraR * sin(cameraH * PI_180) * cos(cameraV * PI_180) + 0.5f;
		eyePos[2] = cameraR * sin(cameraV * PI_180);
		glutPostRedisplay();
	}
	else if (cameraSetting == 2) {
		cameraR = cameraTR + 0.01 * (y - mouseOY);
		if (cameraR < cameraNear) cameraR = cameraNear;
		else if (cameraR > cameraFar) cameraR = cameraFar;
		eyePos[0] = cameraR * cos(cameraH * PI_180) * cos(cameraV * PI_180) + 0.5f;
		eyePos[1] = cameraR * sin(cameraH * PI_180) * cos(cameraV * PI_180) + 0.5f;
		eyePos[2] = cameraR * sin(cameraV * PI_180);
		glutPostRedisplay();
	}
}


void keyboard(unsigned char key, int x, int y)
{
	switch (key) {
		case 27:
			exit(0);
			break;
		case 32:
			systemPaused = !systemPaused;
			break;
		case 'q':
		case 'Q':
			testSwitch = !testSwitch;
			glutPostRedisplay();
			break;
		case 'r':
		case 'R':
			wpNum = 0;
			glutPostRedisplay();
			break;
		case 'w':
		case 'W':
			renderParticles = !renderParticles;
			glutPostRedisplay();
			break;
		case 't':
		case 'T':
			transparent = !transparent;
			glutPostRedisplay();
			break;
		case '`':
			displayMode = 0;
			glutPostRedisplay();
			break;
		case '1':
			displayMode = 1;
			glutPostRedisplay();
			break;
		case '2':
			displayMode = 2;
			glutPostRedisplay();
			break;
		case '3':
			displayMode = 3;
			glutPostRedisplay();
			break;
		case '4':
			displayMode = 4;
			glutPostRedisplay();
			break;		
		case '5':
			displayMode = 5;
			glutPostRedisplay();
			break;		
		case '6':
			displayMode = 6;
			glutPostRedisplay();
			break;		
		case '7':
			displayMode = 7;
			glutPostRedisplay();
			break;		
		default:
			break;
	}
}


int main(int argc, char** argv)
{
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH | GLUT_DOUBLE);
	glutInitWindowPosition(0, 0);
	glutInitWindowSize(windowWidth, windowHeight);
	glutCreateWindow("Wave Particles");

	initializeGL();
	initializeGPU();
//	initializeWaveParticles();

	glutReshapeFunc(reshape);
	glutIdleFunc(idle);
	glutDisplayFunc(display);
	glutMouseFunc(mouse);
	glutMotionFunc(motion);
	glutKeyboardFunc(keyboard);

//	fps.initialize(10);

	glutMainLoop();

	return 0;
}
